package com.cskaoyan.wordmemorize.common.listener;

import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.metadata.data.ReadCellData;
import com.alibaba.excel.read.listener.ReadListener;
import com.alibaba.excel.util.ConverterUtils;
import com.alibaba.excel.util.ListUtils;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.cskaoyan.wordmemorize.common.constant.ResultCodeEnum;
import com.cskaoyan.wordmemorize.common.exception.BusinessException;
import com.cskaoyan.wordmemorize.common.exception.ExcelParseException;
import com.cskaoyan.wordmemorize.converter.TopicConverter;
import com.cskaoyan.wordmemorize.dao.entity.ReviewTopicDO;
import com.cskaoyan.wordmemorize.dao.entity.TopicOptionDO;
import com.cskaoyan.wordmemorize.dao.entity.VocDO;
import com.cskaoyan.wordmemorize.dao.mapper.ReviewTopicOptionMapper;
import com.cskaoyan.wordmemorize.dao.mapper.VocMapper;
import com.cskaoyan.wordmemorize.dto.admin.SingleTopicExcelDTO;
import com.cskaoyan.wordmemorize.dto.admin.VocDTO;
import com.cskaoyan.wordmemorize.service.ReviewTopicOptionService;
import com.cskaoyan.wordmemorize.service.ReviewTopicService;
import lombok.Value;
import lombok.extern.slf4j.Slf4j;


import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

// 有个很重要的点 DemoDataListener 不能被spring管理，要每次读取excel都要new,然后里面用到spring可以构造方法传进去
@Slf4j
public class SingleTopicExcelListener implements ReadListener<SingleTopicExcelDTO> {

    /**
     * 每隔5条存储数据库，实际使用中可以100条，然后清理list ，方便内存回收
     */
    private static final int BATCH_COUNT = 100;
    /**
     * 缓存的数据
     */
    private List<SingleTopicExcelDTO> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);

    /**
     * 假设这个是一个Mapper，当然有业务逻辑这个也可以是一个service。当然如果不用存储这个对象没用。
     */
    private ReviewTopicService reviewTopicService;

    private ReviewTopicOptionService reviewTopicOptionService;

    private TopicConverter topicConverter;

    private VocMapper vocMapper;


    /**
     * 如果使用了spring,请使用这个构造方法。每次创建Listener的时候需要把spring管理的类传进来
     *
     * @param reviewTopicOptionService
     */
    public SingleTopicExcelListener(VocMapper vocMapper,ReviewTopicService reviewTopicService,ReviewTopicOptionService reviewTopicOptionService,TopicConverter topicConverter) {
        this.vocMapper = vocMapper;
        this.reviewTopicService = reviewTopicService;
        this.reviewTopicOptionService = reviewTopicOptionService;
        this.topicConverter = topicConverter;
    }

    /**
     * 这个每一条数据解析都会来调用
     *
     * @param data    one row value. Is is same as {@link AnalysisContext#readRowHolder()}
     * @param context
     */
    @Override
    public void invoke(SingleTopicExcelDTO data, AnalysisContext context) {

        log.info("解析到一条数据:{}", JSON.toJSONString(data));

        cachedDataList.add(data);

        // 达到BATCH_COUNT了，需要去存储一次数据库，防止数据几万条数据在内存，容易OOM
        if (cachedDataList.size() >= BATCH_COUNT) {
            saveData();
            // 存储完成清理 list
            cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
        }

    }

    /**
     * 所有数据解析完成了 都会来调用
     *
     * @param context
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        // 这里也要保存数据，确保最后遗留的数据也存储到数据库
        saveData();
        log.info("所有数据解析完成！");
    }



    public String getAnswer(SingleTopicExcelDTO data) {

        String filedName = "option" + data.getAnswerIndex();

        Field answerField = null;
        try {
            answerField = data.getClass().getDeclaredField(filedName);
            answerField.setAccessible(true);
            return (String) answerField.get(data);
        } catch (Exception e) {
            e.printStackTrace();
           throw new BusinessException(ResultCodeEnum.EXCEL_SAVE_ERROR);
        }

    }

    public TopicOptionDO createTopicOption(String item, Integer isAnswer,Integer optionNumber){
        TopicOptionDO topicOptionDO = new TopicOptionDO();
        topicOptionDO.setItem(item);
        topicOptionDO.setIsAnswer(isAnswer);
        topicOptionDO.setOptionNumber(optionNumber);

        return topicOptionDO;

    }

    /**
     * 加上存储数据库
     */
    private void saveData() {
        log.info("{}条数据，开始存储数据库！", cachedDataList.size());
        List<ReviewTopicDO> topicDOList = new ArrayList<>();
        Map<String,List<TopicOptionDO>> optionsMap = new HashMap<>();
        cachedDataList.forEach(cachedData -> {
            List<TopicOptionDO> optionList = new ArrayList<>();
            String answer = getAnswer(cachedData);
            QueryWrapper<VocDO> queryWrapper = new QueryWrapper<>();
            queryWrapper.eq("content",answer);
            VocDO voc = vocMapper.selectOne(queryWrapper);

            //  为其他不是, 存进List ，为之后插入做准备
            if (!cachedData.getOption1().equals(answer)){
                QueryWrapper<VocDO> queryWrapper1 = new QueryWrapper<>();
                queryWrapper1.eq("content",cachedData.getOption1());
                VocDO voc1 = vocMapper.selectOne(queryWrapper1);
                TopicOptionDO topicOption1 = createTopicOption( voc1.getContent(), 0, 1);
                optionList.add(topicOption1);
            }else {
                TopicOptionDO topicOption1 = createTopicOption(voc.getContent(), 1, 1);
                optionList.add(topicOption1);
            }

            if (!cachedData.getOption2().equals(answer)){
                QueryWrapper<VocDO> queryWrapper2 = new QueryWrapper<>();
                queryWrapper2.eq("content",cachedData.getOption2());
                VocDO voc2 = vocMapper.selectOne(queryWrapper2);
                TopicOptionDO topicOption2 = createTopicOption( voc2.getContent(), 0, 2);
                optionList.add(topicOption2);
            }else {
                TopicOptionDO topicOption2 = createTopicOption( voc.getContent(), 1, 2);
                optionList.add(topicOption2);
            }

            if (!cachedData.getOption3().equals(answer)){
                QueryWrapper<VocDO> queryWrapper3 = new QueryWrapper<>();
                queryWrapper3.eq("content",cachedData.getOption2());
                VocDO voc3 = vocMapper.selectOne(queryWrapper3);
                TopicOptionDO topicOption3 = createTopicOption(voc3.getContent(), 0, 3);
                optionList.add(topicOption3);
            }else {
                TopicOptionDO topicOption3 = createTopicOption(voc.getContent(), 1, 3);
                optionList.add(topicOption3);
            }

            if (!cachedData.getOption4().equals(answer)){
                QueryWrapper<VocDO> queryWrapper4 = new QueryWrapper<>();
                queryWrapper4.eq("content",cachedData.getOption4());
                VocDO voc4 = vocMapper.selectOne(queryWrapper4);
                TopicOptionDO topicOption4 = createTopicOption(voc4.getContent(), 0, 4);
                optionList.add(topicOption4);
            }else {
                TopicOptionDO topicOption4 = createTopicOption(voc.getContent(), 1, 4);
                optionList.add(topicOption4);
            }
            ReviewTopicDO reviewTopicDO = topicConverter.singleTopicExcelDTO2DO(cachedData, voc.getId());
            reviewTopicDO.setVocabulary(answer);
            topicDOList.add(reviewTopicDO);


            optionsMap.put(cachedData.getTopic(),optionList);
        });
        // 题目存储
        reviewTopicService.saveBatch(topicDOList);

        // 答案存储
        topicDOList.forEach(topicDO -> {
            List<TopicOptionDO> topicOptionDOS = optionsMap.get(topicDO.getTopic());
            topicOptionDOS.forEach(topicOptionDO -> topicOptionDO.setTid(topicDO.getId()));
            reviewTopicOptionService.saveBatch(topicOptionDOS);
        });
        log.info("存储数据库成功！");
    }


    /**
     * 1. 校验单词是否存在
     * 2. 存在 返回false ，不存在 返回ture
     * @param word 被校验的单词
     * @return
     */
    public boolean checkWord(String word) {
        LambdaQueryWrapper<VocDO> vocDOWrapper = new LambdaQueryWrapper<>();
        vocDOWrapper.eq(VocDO::getContent, word);
        VocDO vocDO = vocMapper.selectOne(vocDOWrapper);
        if (vocDO == null) {
            return true;
        }

        return false;
    }
}